﻿using System;
using System.Runtime.InteropServices;
using System.Collections.ObjectModel;
using SCG = System.Collections.Generic;

using Nemerle.Assertions;
using Nemerle.Utility;

using GASS.OpenCL;
using GASS.OpenCL.OpenCLDriver;

namespace Nemerle.OpenCL {

    /// <summary>
    /// Description of Program.
    /// </summary>
    [InfoProperty(CLProgramInfo.ReferenceCount, int, Flags(no_cache))]
    [InfoProperty(CLProgramInfo.Source, string)]
    [EqualityByIdPattern]
    public sealed class Program : IDisposable {

        internal this([NotNull] context : Context, source : string, [NotNull] options : ProgramBuildOptions)
            requires !string.IsNullOrEmpty(source)
        {
            this.context = context;
            this.options = options;
            this.handle = ProgramHandle(chk_err_t clCreateProgramWithSource(context.Id, 1, array[source], array[source.Length :> SizeT]));
            this.binaries = ProgramBinaries(this);
            this.build_info = {
                def build_info = SCG.List();
                def devices = options.Devices;
                for(mutable index = 0; index < devices.Count; ++index)
                    build_info.Add(ProgramBuildInfo(this, devices[index], index));
                ReadOnlyCollection(build_info)
            }
            this.build_succeeded = {
                def devices = Array.ConvertAll(options.Devices.ToArray(), _.Id);
                def opt = options.GetCompilerOptions();
                match(clBuildProgram(this.Id, devices.Length :> uint, devices, opt, null, IntPtr.Zero)) {
                    | CLError.Success =>
                        this.build_error = string.Empty;
                        true
                    | error =>
                        this.build_error = error.ToString();
                        false
                }
            }
        }

        [Accessor] build_succeeded : bool;

        [Accessor] build_error : string;

        [Accessor] context : Context;

        [Accessor] options : ProgramBuildOptions;

        [Accessor] build_info : ReadOnlyCollection[ProgramBuildInfo];

        binaries : ProgramBinaries;

        handle : ProgramHandle;

        public Id : CLProgram {
            get { handle.EntityId }
        }

        created_kernels : SCG.List[Kernel] = SCG.List();

        public Kernels : ReadOnlyCollection[Kernel] {
            kernels : LazyValue[ReadOnlyCollection[Kernel]] = lazy({
                when(build_succeeded) {
                    foreach(id in buffered clCreateKernelsInProgram(Id))
                        created_kernels.Add(Kernel(this, id))
                }
                ReadOnlyCollection(created_kernels)
            });
            get { kernels.Value }
        }

        public GetKernel(function_name : string) : Kernel {
            foreach(kernel when kernel.FunctionName == function_name in Kernels) 
                Nemerle.Imperative.Return(kernel);
            null
        }

        #region IDisposable

        public Dispose() : void {
            foreach(kernel in created_kernels)
                kernel.Dispose();
            when(null != handle)
                handle.Close();
        }

        #endregion


        #region Build information

        [InfoProperty(CLProgramBuildInfo.Log, string, Args(device.Id))]
        [InfoProperty(CLProgramBuildInfo.Options, string, Args(device.Id))]
        [InfoProperty(CLProgramBuildInfo.Status, CLBuildStatus, Args(device.Id))]
        public sealed class ProgramBuildInfo {

            internal this(program : Program, device : Device, index : int) {
                this.Id = program.Id;
                this.device = device;
                this.binary = lazy(program.binaries.Binaries[index]);
            }

            Id : CLProgram;

            [Accessor] device : Device;

            binary : LazyValue[array[byte]];

            public Binary : array[byte] {
                get { binary.Value }
            }

        }

        [InfoProperty(CLProgramInfo.BinarySizes, array[SizeT])]
        private sealed class ProgramBinaries {

            public this(program : Program) {
                this.Id = program.Id;
            }

            Id : CLProgram;

            public Binaries : array[array[byte]] {
                binaries : LazyValue[array[array[byte]]] = lazy({
                    def bins = Array.ConvertAll(BinarySizes, s => array(s :> int));
                    def handles = Array.ConvertAll(bins, b => GCHandle.Alloc(b, GCHandleType.Pinned));
                    try {
                        fixed(ptr = Array.ConvertAll(bins, b => Marshal.UnsafeAddrOfPinnedArrayElement(b, 0))) {
                            mutable size_ret : SizeT;
                            CheckError(clGetProgramInfo(Id, CLProgramInfo.Binaries, bins.Length * IntPtr.Size, ptr, ref size_ret));
                        }
                    } finally {
                        Array.ForEach(handles, _.Free())
                    }
                    bins
                });
                get { binaries.Value }
            }

        }

        #endregion

    }

}